package org.zhaolei.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zhaolei.config.RabbitMQConfig;
import org.zhaolei.enums.BizCodeEnum;
import org.zhaolei.enums.CouponStateEnum;
import org.zhaolei.enums.ProductOrderStateEnum;
import org.zhaolei.enums.StockTaskStateEnum;
import org.zhaolei.exception.BizException;
import org.zhaolei.feign.ProductOrderFeignService;
import org.zhaolei.interceptor.LoginInterceptor;
import org.zhaolei.mapper.CouponRecordMapper;
import org.zhaolei.mapper.CouponTaskMapper;
import org.zhaolei.model.CouponRecordDO;
import org.zhaolei.model.CouponRecordMessage;
import org.zhaolei.model.CouponTaskDO;
import org.zhaolei.model.LoginUser;
import org.zhaolei.request.LockCouponRecordRequest;
import org.zhaolei.service.CouponRecordService;
import org.zhaolei.util.JsonData;
import org.zhaolei.vo.CouponRecordVO;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Slf4j
public class CouponRecordServiceImpl implements CouponRecordService {

    @Autowired
    private CouponRecordMapper couponRecordMapper;
    @Autowired
    private CouponTaskMapper couponTaskMapper;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @Autowired
    private ProductOrderFeignService productOrderFeignService;

    @Override
    public Map<String, Object> page(int page, int size) {
        //获取登录用户信息
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        //封装分页信息
        Page<CouponRecordDO> pageInfo = new Page<>(page, size);

        IPage<CouponRecordDO> recordDOIPage = couponRecordMapper.selectPage(pageInfo, new QueryWrapper<CouponRecordDO>()
                .eq("user_id", loginUser.getId())
                .orderByDesc("create_time"));

        Map<String, Object> pageMap = new HashMap<>(3);
        pageMap.put("total_record", recordDOIPage.getTotal());
        pageMap.put("total_page", recordDOIPage.getPages());
        pageMap.put("current_data", recordDOIPage.getRecords().stream().map(
                this::beanProcess
        ).collect(Collectors.toList()));

        return pageMap;
    }

    @Override
    public CouponRecordVO getCouponRecordDetail(long couponRecordId) {
        //防止水平越权，要校验user id
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        CouponRecordDO couponRecordDO = couponRecordMapper.selectOne(new QueryWrapper<CouponRecordDO>()
                .eq("id", couponRecordId)
                .eq("user_id", loginUser.getId()));
        if (couponRecordDO == null) {
            return null;
        }

        return beanProcess(couponRecordDO);
    }

    /**
     * 锁定优惠券
     *
     * 1. 锁定优惠券记录
     * 2. task表插入记录
     * 3. 发送延迟消息
     * @param lockCouponRecordRequest
     * @return
     */
    @Override
    public JsonData lockCouponRecords(LockCouponRecordRequest lockCouponRecordRequest) {
        //先获取用户、订单id和优惠券ids
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        String orderOutTradeNo = lockCouponRecordRequest.getOrderOutTradeNo();
        //这里的coupon id是coupon_record表里的id，其对应的优惠券可以有重复的
        List<Long> lockCouponRecordIds = lockCouponRecordRequest.getLockCouponRecordIds();
        //与我原来的思路不一样，这一步优惠券已经发放完了，但还没消费，要从用户的记录表中改为已消费！！！！
        int updateRows = couponRecordMapper.lockUseStateBatch(loginUser.getId(),
                CouponStateEnum.USED.name(), lockCouponRecordIds);

        //把lockCouponRecordIds都变成CouponTaskDO
        List<CouponTaskDO> couponTaskDOList = lockCouponRecordIds.stream().map(obj->{
            CouponTaskDO couponTaskDO = new CouponTaskDO();
            couponTaskDO.setCreateTime(new Date());
            couponTaskDO.setOutTradeNo(orderOutTradeNo);
            couponTaskDO.setCouponRecordId(obj);
            couponTaskDO.setLockState(StockTaskStateEnum.LOCK.name());
            return couponTaskDO;
        }).collect(Collectors.toList());

        //可不可以用couponTaskMapper.insert(couponTaskDO)来反复多次的插入呢？？？
        int insertRows = couponTaskMapper.insertBatch(couponTaskDOList);
        log.info("优惠券记录锁定updateRows={}", updateRows);
        log.info("新增优惠券记录task insertRows={}", insertRows);

        if(lockCouponRecordIds.size() == insertRows && insertRows == updateRows) {
            //发送延迟消息
            for (CouponTaskDO couponTaskDO : couponTaskDOList) {
                //遍历所有优惠券，并转换成CouponRecordMessage对象
                CouponRecordMessage couponRecordMessage = new CouponRecordMessage();
                couponRecordMessage.setOutTradeNo(orderOutTradeNo);
                couponRecordMessage.setTaskId(couponTaskDO.getId());

                rabbitTemplate.convertAndSend(rabbitMQConfig.getEventExchange(),
                        rabbitMQConfig.getCouponReleaseDelayRoutingKey(), couponRecordMessage);
                log.info("发送延迟消息成功，消息体={}", couponRecordMessage.toString());
            }

            return JsonData.buildSuccess();
        } else {
            throw new BizException(BizCodeEnum.COUPON_RECORD_LOCK_FAIL);
        }

    }

    /**
     * 接收延迟消息释放优惠券，每一个消息对应着一张优惠券
     * 问题：两个订单下单的时间间隔很短，上一单的优惠券还没有被消费，下一单会重复使用吗？不会，因为优惠券已经被锁住了
     * @param couponRecordMessage
     * @return
     */
    @Override
    public boolean releaseCouponRecord(CouponRecordMessage couponRecordMessage) {
        //每一个couponTask对应着一个couponRecordMessage
        //先查询task是否存在
        CouponTaskDO couponTaskDO = couponTaskMapper.selectOne(new QueryWrapper<CouponTaskDO>()
                .eq("id", couponRecordMessage.getTaskId()));
        if (couponTaskDO == null) {
            log.warn("工作单不存在，消息：{}", couponRecordMessage);
            return true;
        }
        //工作单存在，且状态是LOCK才处理
        if (couponTaskDO.getLockState().equals(StockTaskStateEnum.LOCK.name())) {

            //查询订单状态
            JsonData jsonData = productOrderFeignService.queryProductOrderState(couponRecordMessage.getOutTradeNo());

            if (jsonData.getCode() == 0) {
                //正常响应，判断订单的状态，是新订单还是已支付的订单
                String state = jsonData.getData().toString();
                if (ProductOrderStateEnum.NEW.name().equalsIgnoreCase(state)) {
                    //状态是NEW新建状态，则返回给消息队，列重新投递
                    log.warn("订单状态是NEW,返回给消息队列，重新投递:{}", couponRecordMessage);
                    return false;
                }

                if (ProductOrderStateEnum.PAY.name().equalsIgnoreCase(state)) {
                    //如果已经支付，修改task状态为finish
                    couponTaskDO.setLockState(StockTaskStateEnum.FINISH.name());
                    couponTaskMapper.update(couponTaskDO,new QueryWrapper<CouponTaskDO>().eq("id",couponRecordMessage.getTaskId()));
                    log.info("订单已经支付，修改库存锁定工作单FINISH状态:{}", couponRecordMessage);
                    return true;
                }
            }

            //订单不存在，或者订单被取消，修改task状态为CANCEL,恢复优惠券使用记录为NEW
            log.warn("订单不存在，或者订单被取消，确认消息,修改task状态为CANCEL,恢复优惠券使用记录为NEW,message:{}",couponRecordMessage);
            //couponTaskDO是锁定优惠券时创建并插入的，现在释放优惠券只要更改其状态就行
            couponTaskDO.setLockState(StockTaskStateEnum.CANCEL.name());

            couponTaskMapper.update(couponTaskDO,new QueryWrapper<CouponTaskDO>().eq("id",couponRecordMessage.getTaskId()));
            //恢复优惠券记录是NEW状态，不涉及到更改优惠券的库存的问题
            couponRecordMapper.updateState(couponTaskDO.getCouponRecordId(),CouponStateEnum.NEW.name());

            return true;

        } else {
            log.warn("工作单状态不是LOCK,state={},消息体={}", couponTaskDO.getLockState(), couponRecordMessage);
            return true;
        }
    }

    private CouponRecordVO beanProcess(CouponRecordDO couponRecordDO) {
        CouponRecordVO couponRecordVO = new CouponRecordVO();
        BeanUtils.copyProperties(couponRecordDO, couponRecordVO);
        return couponRecordVO;
    }
}
